package com.smsc.headend.task.engine.handler.impl.ansi;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.errorcode.TaskHandlerErrorCode;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.protocol.DeviceData;
import com.smsc.headend.module.protocol.ansi.AnsiByteBuffer;
import com.smsc.headend.module.protocol.ansi.AnsiData;
import com.smsc.headend.module.report.entity.LogScheduleTaskDevice;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.module.task.dto.BlockInfo;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.exception.UDISTaskHandlerException;
import com.smsc.headend.task.engine.handler.TaskHandler;
import com.smsc.headend.task.engine.service.DataProcessService;
import com.smsc.headend.task.engine.service.DeviceStatusService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.calc.RdEnergyDailyService;
import com.smsc.headend.task.engine.service.collection.CollectTaskService;
import com.smsc.headend.task.engine.service.task.LogScheduleTaskDeviceService;
import com.smsc.headend.task.engine.service.task.impl.TaskServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Slf4j
public class GetOffsetHandler implements TaskHandler {
    @Autowired
    FeignAssetManagementService assetManagementService;
    @Autowired
    KafkaTemplate kafkaTemplate;
    @Autowired
    DataProcessService dataProcessService;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    TaskServiceImpl taskService;
    @Autowired
    AssetService assetService;
    @Autowired
    LogScheduleTaskDeviceService logScheduleTaskDeviceService;
    @Autowired
    DeviceStatusService deviceStatusService;
    @Autowired
    RdEnergyDailyService rdEnergyDailyService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    CollectTaskService collectTaskService;

    @Override
    public int handle(Task task, AtomicTask atomicTask, DeviceData deviceData, String actionResult) throws UDISTaskHandlerException {
        if (!(deviceData instanceof AnsiData)) {
            log.error("result={}, data={}", actionResult, deviceData);
            throw new UDISTaskHandlerException(TaskHandlerErrorCode.DEVICE_DATA_TYPE_NOT_SUPPORT.toString());
        }
        Meter meter = assetService.getMeterById(task.getMeterId());
        AnsiData ansiData = (AnsiData) deviceData;
        log.info("get offset {}", ansiData.getAnsiByteBuffer());
        AnsiByteBuffer dataBuffer = ansiData.getAnsiByteBuffer();
        dataBuffer.position(3);
        int year = dataBuffer.getUInt8() + 2000;
        int month = dataBuffer.getUInt8();
        int day = dataBuffer.getUInt8();
        int hour = dataBuffer.getUInt8();
        int minute = dataBuffer.getUInt8();
        LocalDateTime dateTime = LocalDateTime.of(year, month, day, hour, minute, 0);
        log.info("year:{}, month:{}, day:{}, hour:{}, minute:{}", year, month, day, hour, minute);
        AtomicTaskState atomicTaskState = taskDAO.hashGetAtomicTaskState(atomicTask.getAtomicTaskNo());
        Map ansiTaskData = atomicTaskState.getData();
        Integer nbrBlkIntsSet1 = MapUtil.getInt(ansiTaskData, "NBR_BLK_INTS_SET1");
        Integer maxIntTimeSet1 = MapUtil.getInt(ansiTaskData, "MAX_INT_TIME_SET1");
        Integer nbrChnsSet1 = MapUtil.getInt(ansiTaskData, "NBR_CHNS_SET1");
        Long preBlockLastTime = dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() / 1000;
        Long currentBlockFirstTime = preBlockLastTime + maxIntTimeSet1 * 60;
        Long currentBlockLastTime = preBlockLastTime + nbrBlkIntsSet1 * maxIntTimeSet1 * 60;

        Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
        Map<String, Object> initialedMap = null;
        try {
            initialedMap = collectTaskService.scheduleTaskTimeInit(task, parameterMap, meter, task.getTaskType());
        } catch (UDISTaskExecutorException e) {
            throw new UDISTaskHandlerException("IllegalStartTime");
        }
        Long profileStartTime = MapUtil.getLong(initialedMap, "profileStartTime");
        Long profileEndTime = MapUtil.getLong(initialedMap, "profileEndTime");
        Integer currentOffset = MapUtil.getInt(ansiTaskData, "CURRENT_OFFSET");
        Integer blockSize = MapUtil.getInt(ansiTaskData, "BLOCK_SIZE");
        Integer currentBlockNum = MapUtil.getInt(ansiTaskData, "CURRENT_BLOCK_NUM");
        Integer expectedBlockOffset = currentOffset - blockSize;
        ansiTaskData.put("CURRENT_OFFSET", expectedBlockOffset);
        ansiTaskData.put("CURRENT_BLOCK_NUM", currentBlockNum - 1);

        boolean exitFlag = false;
        if (isContain(profileStartTime, profileEndTime, currentBlockFirstTime, currentBlockLastTime)) {
            atomicTaskState.getMatchedBlockNumbers().add(new BlockInfo(currentBlockFirstTime, currentBlockNum, currentOffset + 5 + 4 * nbrChnsSet1));
            if(currentBlockNum == 0) {
                exitFlag = true;
                log.info("the block num {}, the block date time {}, is the first block, end to find the block.", currentBlockNum, DateUtil.formatDateTime(new Date(preBlockLastTime * 1000)));
            } else {
                log.info("the block num {}, the block date time {}, continue to find the block", currentBlockNum, DateUtil.formatDateTime(new Date(preBlockLastTime * 1000)));
                taskDAO.hashSetAtomicTaskState(atomicTask.getAtomicTaskNo(), task, atomicTaskState.getRetryTimes(), atomicTaskState);
            }
        } else {
            if (!atomicTaskState.getMatchedBlockNumbers().isEmpty()) {
                log.info("the block num {}, the block date time {}, not matched, end to find the block", currentBlockNum, DateUtil.formatDateTime(new Date(preBlockLastTime * 1000)));
                exitFlag = true;
            } else {
                if (expectedBlockOffset > 0) {
                    log.info("the block num {}, the block date time {}, not matched, continue to find the block", currentBlockNum, DateUtil.formatDateTime(new Date(preBlockLastTime * 1000)));
                    exitFlag = false;
                } else {
                    log.info("the block num {}, the block date time {}, not matched, ended, failed to find the block", currentBlockNum, DateUtil.formatDateTime(new Date(preBlockLastTime * 1000)));
                    throw new UDISTaskHandlerException("not find matched block");
                }
            }
        }
        if(exitFlag) {
            String ansiTaskDataKey = RedisKeys.getAnsiTaskDataKey(task.getTaskNo());
            List<BlockInfo> blockNum = CollUtil.sortByProperty(atomicTaskState.getMatchedBlockNumbers(), "blockNum");
            log.info("the matched block num {}", JSONUtil.toJsonStr(blockNum));
            Map ansiMap = redisUtils.hmget(ansiTaskDataKey);
            ansiMap.put("MATCHED_BLOCK_NUMS", blockNum);
            ansiMap.put("profileStartTime", profileStartTime);
            ansiMap.put("profileEndTime", profileEndTime);
            redisUtils.hmset(ansiTaskDataKey, ansiMap);

            return 1;
        }
        return 0;
    }

    private boolean isContain(Long profileStartTime, Long profileEndTime, Long blockStartTime, Long blockEndTime) {
        // a0 包在了 b0 ~ b1 之间
        if (profileStartTime >= blockStartTime && profileStartTime <= blockEndTime) return true;

        // b0 包在了 a0 ~ a1 之间
        if (profileStartTime <= blockStartTime && profileEndTime >= blockStartTime) return true;

        return false;
    }

    @Override
    public int handleFailure(Task task, AtomicTask atomicTask, String message) {
        log.info("error msg:{}", message);
        if (task.getIsODR()) {
            Map<String, Object> param = getParameterMap(task, atomicTask);
            Long lpDataItemId = Long.valueOf(String.valueOf(param.get("dataItemId")));
            // key 回曲线的dataItemId给web
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), Convert.toStr(lpDataItemId), message, 3600l, false);
        }
        if (StrUtil.equalsIgnoreCase(task.getTaskType(), TaskType.ScheduleRead_Profile.name())) {
            logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), LogScheduleTaskDevice.StatusDescEnum.DecodeDataFailed.getCode());
        }
        return -1;
    }

}

